package org.sf.dynamic;

import sf.common.exception.SmallOrmException;
import sf.spring.util.CollectionUtils;
import sf.tools.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 通用Example查询对象
 */
public class Example implements IDynamicTableName {

    protected boolean distinct;

    protected boolean exists;

    protected boolean notNull;

    protected boolean forUpdate;

    //查询字段
    protected Set<String> selectColumns;

    //排除的查询字段
    protected Set<String> excludeColumns;

    protected String countColumn;

    protected List<Criteria> oredCriteria;

    protected Map<String, Order> orderMap;

    protected Class<?> entityClass;

    protected EntityTable table;
    //属性和列对应
    protected Map<String, EntityColumn> propertyMap;
    //动态表名
    protected String tableName;

    /**
     * 默认exists为true
     * @param entityClass
     */
    public Example(Class<?> entityClass) {
        this(entityClass, true);
    }

    /**
     * 带exists参数的构造方法，默认notNull为false，允许为空
     * @param entityClass
     * @param exists      - true时，如果字段不存在就抛出异常，false时，如果不存在就不使用该字段的条件
     */
    public Example(Class<?> entityClass, boolean exists) {
        this(entityClass, exists, false);
    }

    /**
     * 带exists参数的构造方法
     * @param entityClass
     * @param exists      - true时，如果字段不存在就抛出异常，false时，如果不存在就不使用该字段的条件
     * @param notNull     - true时，如果值为空，就会抛出异常，false时，如果为空就不使用该字段的条件
     */
    public Example(Class<?> entityClass, boolean exists, boolean notNull) {
        this.exists = exists;
        this.notNull = notNull;
        oredCriteria = new ArrayList<Criteria>();
        this.entityClass = entityClass;
//        table = EntityHelper.getEntityTable(entityClass);
        //根据李领北建议修改#159
        propertyMap = table.getPropertyMap();
        this.orderMap = new LinkedHashMap<>(4);
    }

    public Example orderBy(String property, Order order) {
        this.orderMap.put(property, order);
        return this;
    }

    public Example orderBy(String... properties) {
        return orderByAsc(properties);
    }

    public Example orderByAsc(String... properties) {
        for (String field : properties) {
            this.orderMap.put(field, Order.ASC);
        }
        return this;
    }

    public Example orderByDesc(String... properties) {
        for (String field : properties) {
            this.orderMap.put(field, Order.DESC);
        }
        return this;
    }

    public String getOrderByClause() {
        StringBuilder sb = new StringBuilder();
        boolean flag = false;
        for (Map.Entry<String, Order> entry : orderMap.entrySet()) {
            String field = entry.getKey();
            String name = "";
            if (propertyMap.containsKey(field)) {
                name = propertyMap.get(field).getColumn();
                if (StringUtils.isNotBlank(name)) {
                    sb.append(flag ? "," : "").append(name).append(" ")
                            .append(entry.getValue().name());
                    flag = true;
                }
            }
        }
        return sb.toString();
    }

    /**
     * 排除查询字段，优先级低于 selectProperties
     * @param properties 属性名的可变参数
     * @return
     */
    public Example excludeProperties(String... properties) {
        if (properties != null && properties.length > 0) {
            if (this.excludeColumns == null) {
                this.excludeColumns = new LinkedHashSet<String>();
            }
            for (String property : properties) {
                if (propertyMap.containsKey(property)) {
                    this.excludeColumns.add(propertyMap.get(property).getColumn());
                } else {
                    throw new SmallOrmException("类 " + entityClass.getSimpleName() + " 不包含属性 \'" + property + "\'，或该属性被@Transient注释！");
                }
            }
        }
        return this;
    }

    /**
     * 指定要查询的属性列 - 这里会自动映射到表字段
     * @param properties
     * @return
     */
    public Example selectProperties(String... properties) {
        if (properties != null && properties.length > 0) {
            if (this.selectColumns == null) {
                this.selectColumns = new LinkedHashSet<String>();
            }
            for (String property : properties) {
                if (propertyMap.containsKey(property)) {
                    this.selectColumns.add(propertyMap.get(property).getColumn());
                } else {
                    throw new SmallOrmException("类 " + entityClass.getSimpleName() + " 不包含属性 \'" + property + "\'，或该属性被@Transient注释！");
                }
            }
        }
        return this;
    }

    public void or(Criteria criteria) {
        criteria.setAndOr("or");
        oredCriteria.add(criteria);
    }

    public Criteria or() {
        Criteria criteria = createCriteriaInternal();
        criteria.setAndOr("or");
        oredCriteria.add(criteria);
        return criteria;
    }

    public void and(Criteria criteria) {
        criteria.setAndOr("and");
        oredCriteria.add(criteria);
    }

    public Criteria and() {
        Criteria criteria = createCriteriaInternal();
        criteria.setAndOr("and");
        oredCriteria.add(criteria);
        return criteria;
    }

    public Criteria createCriteria() {
        Criteria criteria = createCriteriaInternal();
        if (oredCriteria.size() == 0) {
            criteria.setAndOr("and");
            oredCriteria.add(criteria);
        }
        return criteria;
    }

    protected Criteria createCriteriaInternal() {
        Criteria criteria = new Criteria(this, propertyMap, exists, notNull);
        return criteria;
    }

    public void clear() {
        oredCriteria.clear();
        orderMap.clear();
        distinct = false;
    }

    private String property(String property) {
        if (propertyMap.containsKey(property)) {
            return property;
        } else if (exists) {
            throw new SmallOrmException("当前实体类不包含名为" + property + "的属性!");
        } else {
            return null;
        }
    }

    protected abstract static class GeneratedCriteria {
        protected List<Criterion> criteria;
        //字段是否必须存在
        protected boolean exists;
        //值是否不能为空
        protected boolean notNull;
        //连接条件
        protected String andOr;
        //属性和列对应
        protected Map<String, EntityColumn> propertyMap;
        protected Example example;

        protected GeneratedCriteria(Example example, Map<String, EntityColumn> propertyMap, boolean exists, boolean notNull) {
            super();
            this.exists = exists;
            this.notNull = notNull;
            criteria = new ArrayList<Criterion>();
            this.propertyMap = propertyMap;
            this.example = example;
        }

        private String column(String property) {
            if (propertyMap.containsKey(property)) {
                return propertyMap.get(property).getColumn();
            } else if (exists) {
                throw new SmallOrmException("当前实体类不包含名为" + property + "的属性!");
            } else {
                return null;
            }
        }

        private String property(String property) {
            if (propertyMap.containsKey(property)) {
                return property;
            } else if (exists) {
                throw new SmallOrmException("当前实体类不包含名为" + property + "的属性!");
            } else {
                return null;
            }
        }

        protected void addCriterion(String condition) {
            if (condition == null) {
                throw new SmallOrmException("Value for condition cannot be null");
            }
            if (condition.startsWith("null")) {
                return;
            }
            criteria.add(new Criterion(condition));
        }

        protected void addCriterion(String condition, String property) {
            if (condition == null) {
                throw new SmallOrmException("Value for condition cannot be null");
            }
            if (condition.startsWith("null")) {
                return;
            }
            criteria.add(new Criterion(condition, property));
        }

        protected void addCriterion(String condition, Object value, String property) {
            if (value == null) {
                if (notNull) {
                    throw new SmallOrmException("Value for " + property + " cannot be null");
                } else {
                    return;
                }
            }
            if (property == null) {
                return;
            }
            criteria.add(new Criterion(condition, value));
        }

        protected void addCriterion(String condition, Object value1, Object value2, String property) {
            if (value1 == null || value2 == null) {
                if (notNull) {
                    throw new SmallOrmException("Between values for " + property + " cannot be null");
                } else {
                    return;
                }
            }
            if (property == null) {
                return;
            }
            criteria.add(new Criterion(condition, value1, value2));
        }


        public Criteria isNull(String property) {
            addCriterion(" is null");
            return (Criteria) this;
        }

        public Criteria isNotNull(String property) {
            addCriterion(" is not null");
            return (Criteria) this;
        }

        public Criteria eq(String property, Object value) {
            addCriterion(" =?", value, property(property));
            return (Criteria) this;
        }

        public Criteria ne(String property, Object value) {
            addCriterion(" <>?", value, property(property));
            return (Criteria) this;
        }

        public Criteria gt(String property, Object value) {
            addCriterion(" >", value, property(property));
            return (Criteria) this;
        }

        public Criteria ge(String property, Object value) {
            addCriterion(" >=?", value, property(property));
            return (Criteria) this;
        }

        public Criteria lt(String property, Object value) {
            addCriterion(" <?", value, property(property));
            return (Criteria) this;
        }

        public Criteria le(String property, Object value) {
            addCriterion(" <=?", value, property(property));
            return (Criteria) this;
        }

        public Criteria in(String property, Collection<?> values) {
            addCriterion(" in" + toIn(values), values, property(property));
            return (Criteria) this;
        }

        protected String toIn(Collection<?> values) {
            StringBuilder sb = new StringBuilder();
            if (CollectionUtils.isNotEmpty(values)) {
                sb.append("(");
                boolean f = false;
                for (int i = 0; i < values.size(); i++) {
                    sb.append(f ? "," : "").append("?");
                    f = true;
                }
                sb.append(") ");
            }
            return sb.toString();
        }

        public Criteria notIn(String property, Collection<?> values) {
            addCriterion(" not in" + toIn(values), values, property(property));
            return (Criteria) this;
        }

        public Criteria between(String property, Object value1, Object value2) {
            addCriterion(" between ? and ?", value1, value2, property(property));
            return (Criteria) this;
        }

        public Criteria notBetween(String property, Object value1, Object value2) {
            addCriterion(" not between ? and ?", value1, value2, property(property));
            return (Criteria) this;
        }

        public Criteria like(String property, String value) {
            addCriterion("  like ?", value, property(property));
            return (Criteria) this;
        }

        public Criteria like(String property, String value, char escape) {
            addCriterion("  like ? escape '" + escape + "'", value, property(property));
            return (Criteria) this;
        }

        public Criteria like(String property, String value, boolean escape) {
            addCriterion("  like ?" + (escape ? " escape '/'" : ""), value, property(property));
            return (Criteria) this;
        }

        public Criteria notLike(String property, String value) {
            addCriterion("  not like ?", value, property(property));
            return (Criteria) this;
        }

        public Criteria notLike(String property, String value, char escape) {
            addCriterion("  not like ? escape '" + escape + "'", value, property(property));
            return (Criteria) this;
        }

        public Criteria notLike(String property, String value, boolean escape) {
            addCriterion("  not like ?" + (escape ? " escape '/'" : ""), value, property(property));
            return (Criteria) this;
        }

        /**
         * 手写条件
         * @param condition 例如 "length(countryname)<5"
         * @return
         */
        public Criteria addCondition(String condition) {
            addCriterion(condition);
            return (Criteria) this;
        }

        /**
         * 手写左边条件，右边用value值
         * @param condition 例如 "length(countryname)="
         * @param value     例如 5
         * @return
         */
        public Criteria addCondition(String condition, Object value) {
            criteria.add(new Criterion(condition, value));
            return (Criteria) this;
        }

        public Criteria or() {
            addCondition(" or ");
            return (Criteria) this;
        }

        public Criteria and() {
            addCondition(" and ");
            return (Criteria) this;
        }

        public Criteria leftP() {
            addCondition("(");
            return (Criteria) this;
        }

        public Criteria rightP() {
            addCondition(")");
            return (Criteria) this;
        }

        public List<Criterion> getAllCriteria() {
            return criteria;
        }

        public String getAndOr() {
            return andOr;
        }

        public void setAndOr(String andOr) {
            this.andOr = andOr;
        }

        public List<Criterion> getCriteria() {
            return criteria;
        }

        public boolean isValid() {
            return criteria.size() > 0;
        }
    }

    public static class Criteria extends GeneratedCriteria {

        protected Criteria(Example example, Map<String, EntityColumn> propertyMap, boolean exists, boolean notNull) {
            super(example, propertyMap, exists, notNull);
        }
    }

    public enum ValueType {
        noValue, singleValue, betweenValue, listValue
    }

    public enum Order {
        ASC, DESC
    }

    public static class Criterion {
        private String condition;

        private Object value;

        private Object secondValue;

        private ValueType valueType;

        private String typeHandler;

        protected Criterion(String condition) {
            super();
            this.condition = condition;
            this.typeHandler = null;
            this.valueType = ValueType.noValue;
        }

        protected Criterion(String condition, String typeHandler) {
            super();
            this.condition = condition;
            this.typeHandler = typeHandler;
            this.valueType = ValueType.singleValue;
        }

        protected Criterion(String condition, Object value, String typeHandler) {
            super();
            this.condition = condition;
            this.value = value;
            this.typeHandler = typeHandler;
            if (value instanceof Collection<?>) {
                this.valueType = ValueType.listValue;
            } else {
                this.valueType = ValueType.singleValue;
            }
        }

        protected Criterion(String condition, Object value) {
            this(condition, value, null);
        }

        protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
            super();
            this.condition = condition;
            this.value = value;
            this.secondValue = secondValue;
            this.typeHandler = typeHandler;
            this.valueType = ValueType.betweenValue;
        }

        protected Criterion(String condition, Object value, Object secondValue) {
            this(condition, value, secondValue, null);
        }

        public String getCondition() {
            return condition;
        }

        public Object getSecondValue() {
            return secondValue;
        }

        public String getTypeHandler() {
            return typeHandler;
        }

        public Object getValue() {
            return value;
        }
    }


    public String getCountColumn() {
        return countColumn;
    }

    @Override
    public String getDynamicTableName() {
        return tableName;
    }

    public Class<?> getEntityClass() {
        return entityClass;
    }

    public List<Criteria> getOredCriteria() {
        return oredCriteria;
    }

    public Set<String> getSelectColumns() {
        if (selectColumns != null && selectColumns.size() > 0) {
            //不需要处理
        } else if (excludeColumns != null && excludeColumns.size() > 0) {
            Collection<EntityColumn> entityColumns = propertyMap.values();
            selectColumns = new LinkedHashSet<String>(entityColumns.size() - excludeColumns.size());
            for (EntityColumn column : entityColumns) {
                if (!excludeColumns.contains(column.getColumn())) {
                    selectColumns.add(column.getColumn());
                }
            }
        }
        return selectColumns;
    }

    public boolean isDistinct() {
        return distinct;
    }

    public void setDistinct(boolean distinct) {
        this.distinct = distinct;
    }

    public boolean isForUpdate() {
        return forUpdate;
    }

    public void setForUpdate(boolean forUpdate) {
        this.forUpdate = forUpdate;
    }

    /**
     * 指定 count(property) 查询属性
     * @param property
     */
    public void setCountProperty(String property) {
        if (propertyMap.containsKey(property)) {
            this.countColumn = propertyMap.get(property).getColumn();
        }
    }

    /**
     * 设置表名
     * @param tableName
     */
    public void setTableName(String tableName) {
        this.tableName = tableName;
    }
}